package impl_v2;

import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import model.AuctionInformation;
import model.AuctionPrivate;
import model.AuctionPublic;
import model.Bid;
import model.Item;
import model.WinnerInformation;
import model.exception.InvalidAuctionIDException;
import model.exception.InvalidPriceException;
import model.exception.UnavailableAuctionException;
import model.exception.WrongKeyException;
import model.factory.Factory;
import model.server.AuctioningSystem2;

import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.ResponseMode;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.util.RspList;

public class AuctioningSystemImpl2 extends UnicastRemoteObject implements
		AuctioningSystem2 {

	private static final long serialVersionUID = 1413576917957878882L;

	private final Factory factory;

	private RpcDispatcher dispatcher;

	private final Map<Integer, AuctionPrivate> listAuctions;
	private int lastID;

	private final List<String> serverAddresses;

	public AuctioningSystemImpl2(Factory factory) throws RemoteException {
		super();
		this.factory = factory;
		this.lastID = 0;

		this.listAuctions = new HashMap<Integer, AuctionPrivate>();
		this.serverAddresses = new ArrayList<String>();
	}

	@Override
	public Map<Integer, AuctionPrivate> getListAuctions() throws RemoteException {
		return listAuctions;
	}

	@Override
	public void addServerAddress(String address) throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("addServerAddressAlone", String.class);
			MethodCall methodCall = new MethodCall(method, address);
			dispatcher.callRemoteMethods(null, methodCall, new RequestOptions(ResponseMode.GET_MAJORITY, 1000));
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in addServerAddress.");
		}
	}

	public void addServerAddressAlone(String address) {
		System.out.println("Added server address : " + address);
		this.serverAddresses.add(address);

		System.out.print("List of all servers : ");
		for (String s : serverAddresses) {
			System.out.print(s + ", ");
		}
		System.out.println();
	}

	@Override
	public synchronized AuctionInformation createAuction(Item item, int startingPrice, int reservePrice) throws RemoteException {
		return createAuction(item, startingPrice, reservePrice, 0);
	}
	
	@Override
	public synchronized AuctionInformation createAuction(Item item, int startingPrice, int reservePrice, int key) throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("createAuctionAlone", Item.class, int.class, int.class, int.class);
			MethodCall methodCall = new MethodCall(method, item, startingPrice, reservePrice, key);
			RspList<AuctionInformation> rspList = dispatcher.callRemoteMethods(null, methodCall, new RequestOptions(
					ResponseMode.GET_MAJORITY, 10000));
			return rspList.getFirst();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in createAuction.");
		}
	}

	public synchronized AuctionInformation createAuctionAlone(Item item, int startingPrice,
			int reservePrice, int key) throws RemoteException {

		int auctionID = this.getLastID();
		this.lastID++;

		AuctionPrivate auction = factory.newAuctionPrivate(reservePrice, key,
				startingPrice, item);

		this.listAuctions.put(auctionID, auction);

		AuctionInformation auctionInformation = factory.newAuctionInformation(
				auctionID, auction.getKey());

		System.out.println("The auction " + auction + " is created");
		return auctionInformation;
	}

	@Override
	public synchronized WinnerInformation closeAuction(int auctionID, int key) throws RemoteException, InvalidAuctionIDException,
			WrongKeyException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("closeAuctionAlone", int.class, int.class);
			MethodCall methodCall = new MethodCall(method, auctionID, key);
			RspList<WinnerInformation> rspList = dispatcher.callRemoteMethods(null, methodCall, new RequestOptions(
					ResponseMode.GET_MAJORITY, 1000));

			return rspList.getFirst();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in closeAuction.");
		}
	}

	public synchronized WinnerInformation closeAuctionAlone(int auctionID, int key) throws RemoteException,
			InvalidAuctionIDException, WrongKeyException {

		if (this.listAuctions.containsKey(auctionID)) {

			AuctionPrivate auction = this.listAuctions.get(auctionID);
			if (auction.getKey() == key) {

				this.listAuctions.get(auctionID).setClosed();
				if (auction.hasBid()) {
					String name = auction.getBid().getName();
					String email = auction.getBid().getEmail();
					int finishingPrice = auction.getBid().getPrice();
					return factory.newBuyerInformation(name, email, finishingPrice);
				} else {
					return null;
				}

			} else {
				throw new WrongKeyException();
			}

		} else {
			throw new InvalidAuctionIDException();
		}

	}

	@Override
	public synchronized Map<Integer, AuctionPublic> listAuctionsPublic() throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("listAuctionsPublicAlone");
			MethodCall methodCall = new MethodCall(method);
			RspList<Map<Integer, AuctionPublic>> rspList = dispatcher.callRemoteMethods(null, methodCall,
					new RequestOptions(ResponseMode.GET_MAJORITY, 1000));
			return rspList.getFirst();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in listAuctionsPublic.");
		}
	}

	public synchronized Map<Integer, AuctionPublic> listAuctionsPublicAlone() throws RemoteException {

		Map<Integer, AuctionPublic> list = new HashMap<Integer, AuctionPublic>();
		for (int key : listAuctions.keySet()) {
			AuctionPrivate auction = listAuctions.get(key);
			list.put(key, getFactory().newAuctionPublic(auction));
		}
		return list;
	}

	@Override
	public synchronized void bid(int auctionID, int price, String name, String email) throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("bidAlone", int.class, int.class, String.class, String.class);
			MethodCall methodCall = new MethodCall(method, auctionID, price, name, email);
			dispatcher.callRemoteMethods(null, methodCall, new RequestOptions(ResponseMode.GET_MAJORITY, 1000));
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in bid.");
		}

	}

	public synchronized void bidAlone(int auctionID, int price, String name, String email) throws RemoteException,
			InvalidAuctionIDException, UnavailableAuctionException, InvalidPriceException
	{
		if (!getListAuctions().containsKey(auctionID)) {
			throw new InvalidAuctionIDException();
		}

		AuctionPrivate auction = getListAuctions().get(auctionID);

		if (auction.isClosed()) {
			throw new UnavailableAuctionException();
		}

		if (auction.getStartingPrice() >= price || (auction.getBid() != null && auction.getBid().getPrice() >= price)) {
			throw new InvalidPriceException();
		}

		Bid newBid = getFactory().newBid(price, name, email);
		auction.setBid(newBid);
		System.out.println("New bid for auction " + auction);
	}

	public int getLastID() {
		return lastID;
	}

	public void setLastID(int lastID) {
		this.lastID = lastID;
	}

	public Factory getFactory() {
		return factory;
	}

	@Override
	public void setDispatcher(RpcDispatcher dispatcher) throws RemoteException {
		this.dispatcher = dispatcher;
	}

	@Override
	public AuctionPublic getAuctionInfo(int auctionId) throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("getAuctionInfoAlone", int.class);
			MethodCall methodCall = new MethodCall(method, auctionId);
			RspList<AuctionPublic> rspList = dispatcher.callRemoteMethods(null, methodCall,
					new RequestOptions(ResponseMode.GET_MAJORITY, 1000));
			return rspList.getFirst();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in getAuctionInfo.");
		}
	}

	public AuctionPublic getAuctionInfoAlone(int auctionId) {
		return listAuctions.get(auctionId);
	}

	@Override
	public List<String> getServerAddresses() throws RemoteException {

		// CallRemoteMethod
		try {
			Method method = getClass().getMethod("getServerAddressesAlone");
			MethodCall methodCall = new MethodCall(method);
			RspList<List<String>> rspList = dispatcher.callRemoteMethods(null, methodCall, new RequestOptions(
					ResponseMode.GET_MAJORITY, 1000));
			return rspList.getFirst();
		} catch (Exception e) {
			e.printStackTrace();
			throw new RemoteException("Error in getServerAddresses.");
		}
	}

	public List<String> getServerAddressesAlone() {
		for (String server : serverAddresses) {
			System.out.println("server id : " + server);
		}
		return this.serverAddresses;
	}
}
